/*
 * Copyright (c) 2011, okazoh_tk. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  - Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *  - Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *  - Neither the name of the copyright holder nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#define ENABLE_TRACE    0
//#define ENABLE_PROFILE  1
//#define ENABLE_LOG_MACRO 0
#define TRACE_DRAW      true
#define TRACE_DIRTY     false

#include "GraphicsTest.h"
#include <stdio.h>

using namespace alkes;

GraphicsTest::GraphicsTest()
: device_(0)
, target_(0)
{
    // Enable fps option
    setOption(AL_ADO_COUNT_FPS, true);
}

GraphicsTest::~GraphicsTest()
{
}

bool GraphicsTest::onPreInit(int argc, char* argv[])
{
    TRACE();
    return true;
}

bool GraphicsTest::onInit(IGraphicsDevice* device)
{
    PROF_BEGIN("GraphicsTest::onInit");
    device_ = restrict_cast<IGraphics2DDevice*>(device);
    target_ = device_->createDefaultRenderTarget();

    LOGD("onInit(%p)", device);
    PROF_END("GraphicsTest::onInit");
    return true;
}

void GraphicsTest::onFin()
{
    target_ = 0;
    device_ = 0;
    LOGD("onFin()");
}

void GraphicsTest::onDraw()
{
    static int current = 0;
    static TestCase test_cases[] = {
        testFill,
        testFill_child,
        testRectangleShape,
        testScissor,
        testLineShape,
        testPolylineShape,
        testImageShape,
        testTransform,
        testDebugText,
    };

    PROF_BEGIN("GraphicsTest::onDraw(%d)", current);
    LOGD("TestCase%d(%#p): latest_fps=%3.3f", current, test_cases[current], getFps());
    test_cases[current](device_, target_);
    PROF_END("GraphicsTest::onDraw(%d)", current);

    PROF_CALL(device_->flip());

    static bool stop_fileout = false;

    if (!stop_fileout)
    {
        char filepath[128];
        sprintf(filepath, "snapshot_%02d.tga", current);
        alkes::ImageFileWriter writer(filepath);
        device_->snapshot(writer);
    }

    current = (current + 1) % AL_LENGTH(test_cases);
    if (current == 0)
        stop_fileout = true;

    TRACE_IF(TRACE_DRAW);
}

void GraphicsTest::onResize(uint16_t new_width, uint16_t new_height)
{
    LOGD("onResize(%3d, %3d)", new_width, new_height);
}

void GraphicsTest::onDirty(const Rect& rect)
{
    TRACE_IF(TRACE_DIRTY);
}

void GraphicsTest::onPress(KeyEvent key)
{
    LOGD("onPress(0x%02x)", key);
}

void GraphicsTest::onRelease(KeyEvent key)
{
    LOGD("onRelease(0x%02x)", key);
}

void GraphicsTest::onDeviceEvent(uint16_t device_no, uint64_t value)
{
    TRACE();
}

void GraphicsTest::onMouseMove(uint16_t x, uint16_t y)
{
    LOGD("onMouseMove(%3d, %3d)", x, y);
}

void GraphicsTest::testFill(IGraphics2DDevice* device, RenderTarget* target)
{
    device->beginDraw2D(target);
    device->fillAll(Color::YELLOW);
    device->endDraw2D();
}

void GraphicsTest::testFill_child(IGraphics2DDevice* device, RenderTarget* target)
{
    device->beginDraw2D(target);

    Rect target_rect(target->getTargetRect());

    device->fillAll(Color::WHITE);

    device->setScissorRect(URect(1, 1, 32, 32));
    device->fill(Color::RED);

    device->setScissorRect(URect(16, 16, 64, 64));
    device->fill(Color::BLUE);

    device->setScissorRect(URect(target_rect.x, target_rect.height / 2, target_rect.width, target_rect.height));
    device->fill(Color::GREEN);

    device->setScissorRect(URect(target_rect.width / 2, target_rect.height / 4, target_rect.width, target_rect.height));
    device->fill(Color::YELLOW);

    device->endDraw2D();
}

void GraphicsTest::testRectangleShape(IGraphics2DDevice* device, RenderTarget* target)
{
    device->beginDraw2D(target);

    // clear first
    device->fillAll(Color::WHITE);

    drawRectangleShapes(device);

    device->endDraw2D();
}

static const Rect s_rectangle_rects[4] = {
    {  1,  1, 50, 50},
    { 16, 19,130, 70},
    { 60,  7, 90, 91},
    {  2, 64, 33, 33}
};

void GraphicsTest::drawRectangleShapes(alkes::IGraphics2DDevice* device)
{
    struct {
        int type;
        Color color1;
        Color color2;
        bool option;
    } rectangles[AL_LENGTH(s_rectangle_rects)] = {
        {0, Color::BLUE,    Color::BLACK, false},
        {1, Color::RED,     Color::BLACK, false},
        {2, Color::GRAY,    Color::NAVY,  false},
        {2, Color::YELLOW,  Color::GREEN, true},
    };

    intrusive_ptr<RectangleShape> shapes(device->createRectangleShape(AL_LENGTH(rectangles)));

    int i;
    for (i = 0; i < AL_LENGTH(rectangles); ++i)
    {
        switch (rectangles[i].type)
        {
        case 0: // fill
            shapes->setFillRectangle(i, s_rectangle_rects[i], rectangles[i].color1);
            break;
        case 1: // line
            shapes->setLineRectangle(i, s_rectangle_rects[i], rectangles[i].color1);
            break;
        case 2: // graidant
            shapes->setGradientFillRectangle(i, s_rectangle_rects[i], rectangles[i].color1, rectangles[i].color2, rectangles[i].option);
            break;
        default:
            break;
        }
    }

    device->drawShape(shapes, 0, AL_LENGTH(rectangles));
}

void GraphicsTest::testScissor(IGraphics2DDevice* device, RenderTarget* target)
{
    device->beginDraw2D(target);

    // clear first
    device->fillAll(Color::WHITE);

    device->setScissorRect(s_rectangle_rects[1]);

    drawRectangleShapes(device);

    device->endDraw2D();
}

void GraphicsTest::testLineShape(IGraphics2DDevice* device, RenderTarget* target)
{
    struct
    {
        Point2D pt0;
        Point2D pt1;
        Color color;
        uint8_t width;
    } lines[] = {
        {{  0,   0}, {256, 256}, Color::RED,    1},
        {{256,   0}, {  0, 256}, Color::BLUE,   2},
        {{-45, -64}, {256, 256}, Color::YELLOW, 3},
        {{-45, 127}, {999, 128}, Color::BLACK,  8},
    };

    intrusive_ptr<LineShape> shapes(device->createLineShape(AL_LENGTH(lines)));

    int i;
    for (i = 0; i < AL_LENGTH(lines); ++i)
    {
        shapes->setLine(i, lines[i].pt0, lines[i].pt1, lines[i].color, lines[i].width);
    }

    device->beginDraw2D(target);

    // clear first
    device->fillAll(Color::WHITE);

    device->drawShape(shapes, 0, AL_LENGTH(lines));

    device->endDraw2D();
}

void GraphicsTest::testPolylineShape(IGraphics2DDevice* device, RenderTarget* target)
{
    intrusive_ptr<PolylineShape> shape(device->createPolylineShape(2));

    const static Point2D s_points0[] = {
        { 10,  10},
        {110,  10},
        {110, 110},
        { 10, 110},
        { 10,  10},
    };

    const static Point2D s_points1[] = {
        { 60,  10},
        {110,  60},
        { 60, 110},
        { 10,  60},
        { 60,  10},
    };

    shape->setPolyline(0, s_points0, AL_LENGTH(s_points0), Color::GREEN);
    shape->setPolyline(1, s_points1, AL_LENGTH(s_points1), Color::PURPLE);

    // draw
    device->beginDraw2D(target);
    device->fillAll(Color::WHITE);
    device->drawShape(shape, 0, 2);
    device->endDraw2D();
}

void GraphicsTest::testImageShape(IGraphics2DDevice* device, RenderTarget* target)
{
    intrusive_ptr<ImageShape> shape(device->createImageShape(1));
    shape->setImage(device->decodeImage("test.png"));
    shape->setParam(Point2D::ZERO);

    // draw
    device->beginDraw2D(target);
    device->fillAll(Color::WHITE);
    device->drawShape(shape);
    device->endDraw2D();
}

void GraphicsTest::testTransform(IGraphics2DDevice* device, RenderTarget* target)
{
    intrusive_ptr<ImageShape> shape(device->createImageShape(1));
    shape->setImage(device->decodeImage("test.png"));
    shape->setParam(Point2D::ZERO);

    device->beginDraw2D(target);
    device->fillAll(Color::WHITE);

    device->pushMatrix();
    {
        device->translate(10, 10);
        device->drawShape(shape);
        device->pushMatrix();
        {
            device->translate(64, 64);
            device->drawShape(shape);
        }
        device->popMatrix();
    }
    device->popMatrix();

    device->pushMatrix();
    {
        device->translate(100, 0);
        device->scale(0.4f);
        device->drawShape(shape);

        device->translate(60, 0);
        device->rotate(45, Vector2D::HALF);
        device->drawShape(shape);
    }
    device->popMatrix();

    device->endDraw2D();
}

void GraphicsTest::testDebugText(IGraphics2DDevice* device, RenderTarget* target)
{
    intrusive_ptr<IGlyphProvider> provider(EmbedGlyphProvider::create());
    provider->setEncoding(IGlyphProvider::AL_ENCODING_UTF8);

    // image
    intrusive_ptr<ImageBuffer> image(device->createImage(100, 58, AL_PIXEL_FORMAT_A8));
    provider->renderText("Embedded text rendering.", *image, 0, 0, true);
    provider->renderText("0123456789", *image, 20, 13, true);
    provider->renderText("abcdefghijklmnopqrstuvwxyz", *image, 0, 26);
    provider->renderText("ABCDEFGHIJKLMNOPQRSTUVWXYZ", *image, 0, 39);
    provider->renderText("!\"#$%&'()", *image, 0, 52);

    // shape
    intrusive_ptr<ImageShape> shape(device->createImageShape(1));
    shape->setImage(image);
    shape->setParam(Point2D::ZERO, Color::GRAY);

    // draw
    device->beginDraw2D(target);
    device->translateForImage(0, 0);
    device->setState(AL_GS_ALPHA_BLEND, true);
    device->setAlphaBlendMode(AL_BLEND_SRCALPHA, AL_BLEND_INVSRCALPHA);
    device->fillAll(Color::WHITE);
    device->drawShape(shape);
    device->endDraw2D();

    (void)shape;
}
